/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.icee.myth.log;

import com.icee.myth.log.message.DBBehaviorGameLogMessage;
import com.icee.myth.log.message.DBSilverGameLogMessage;
import com.icee.myth.log.message.DBCashGameLogMessage;
import com.icee.myth.log.message.DBCreateCharGameLogMessage;
import com.icee.myth.log.message.DBLoginLogoutGameLogMessage;
import com.icee.myth.log.message.DBOnlineNumGameLogMessage;
import com.icee.myth.log.message.DBGoldGameLogMessage;
import com.icee.myth.log.message.FileDebugGameLogMessage;
import com.icee.myth.log.message.GameLogMessage;
import com.icee.myth.log.message.builder.GameLogMessageBuilder;
import com.icee.myth.server.GameServer;
import com.icee.myth.utils.Consts;
import com.icee.myth.utils.LinkedTransferQueue;
import com.icee.myth.utils.StackTraceUtil;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 *
 * @author liuxianke
 */
public class DBLogThreadHandle implements Runnable {
    private final LinkedTransferQueue<GameLogMessage> q = new LinkedTransferQueue<GameLogMessage>();

    private final String dbHost;
    private final String dbNamePrefix;
    private Connection dbConnection;    // 数据库连接
    private int keepAliveInterval;
    private boolean connected;          // 是否已连接

    private long realPrevTime;
    private long realCurrTime;
    private int prevSleepTime = 0;

    public boolean shutdown = false;

    public DBLogThreadHandle(String dbHost, String dbNamePrefix) {
        this.dbHost = dbHost;
        this.dbNamePrefix = dbNamePrefix;
        keepAliveInterval = Consts.DB_KEEPALIVE_INTERVAL;
    }

    private void connectToDB() {
        try {
            Class.forName("com.mysql.jdbc.Driver");

            if (dbConnection != null) {
                dbConnection.close();
                dbConnection = null;
                connected = false;
            }

            StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String dbName = dbNamePrefix+dateStringBuffer.toString();
            String dbName = dbNamePrefix;
            dbConnection = DriverManager.getConnection("jdbc:mysql://" + dbHost + "/"+dbName+"?useUnicode=true&characterEncoding=UTF-8", "mythtest", "jkY5qmGKVcRs4nST");
            connected = true;
            keepAliveInterval = Consts.DB_KEEPALIVE_INTERVAL;
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } catch (ClassNotFoundException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        }
    }

    public void log(GameLogMessage message) {
        if (connected) {
            q.offer(message);
        } else {
            System.out.println("Warning: Can't write log because logDB disconnected!");
        }
    }

    public void run() {
        realPrevTime = realCurrTime = System.currentTimeMillis();

        // 连接数据库
        connectToDB();

        while (!shutdown) {
            if (connected) {
                GameLogMessage message = q.poll();
                while (message != null) {
                    switch (message.type) {
                        case GAMELOGTYPE_DB_BEHAVIOR: {
                            handleDBBehaviorGameLogMessage((DBBehaviorGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_CREATECHAR: {
                            handleDBCreateCharGameLogMessage((DBCreateCharGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_LOGINLOGOUT: {
                            handleDBLoginLogoutGameLogMessage((DBLoginLogoutGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_ONLINENUM: {
                            handleDBOnlineNumGameLogMessage((DBOnlineNumGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_CASH: {
                            handleDBCashGameLogMessage((DBCashGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_GOLD: {
                            handleDBGoldGameLogMessage((DBGoldGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_DB_SILVER: {
                            handleDBSilverGameLogMessage((DBSilverGameLogMessage) message);
                            break;
                        }
                        default: {
                            // TODO: 写错误信息
                            break;
                        }
                    }
                    message = q.poll();
                }

                realCurrTime = System.currentTimeMillis();
                int difftime = (int) (realCurrTime - realPrevTime);
                if ((realCurrTime + Consts.JET_LAG)/Consts.MILSECOND_ONE_DAY > (realPrevTime + Consts.JET_LAG)/Consts.MILSECOND_ONE_DAY) {
                    connectToDB();
                }
                realPrevTime = realCurrTime;

                // diff (D0) include time of previous sleep (d0) + tick time (t0)
                // we want that next d1 + t1 == WORLD_SLEEP_CONST
                // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
                // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
                if (difftime <= Consts.LOG_SLEEP_CONST + prevSleepTime) {
                    prevSleepTime = Consts.LOG_SLEEP_CONST + prevSleepTime - difftime;
                } else {
                    prevSleepTime = 10;
                }

                try {
                    Thread.sleep(prevSleepTime);
                } catch (InterruptedException e) {
                }

                if (keepAliveInterval <= 0) {
                    dbKeepAlive();
                    keepAliveInterval = Consts.DB_KEEPALIVE_INTERVAL;
                } else {
                    keepAliveInterval -= difftime;
                }
            } else {
                // TODO: 写错误信息
                System.out.println("Warning: DBLog wrong because log DB can't connect!");
                realPrevTime = realCurrTime = System.currentTimeMillis();
                connectToDB();

                try {
                    // 一秒重连一次
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
        }
    }

    private void dbKeepAlive() {
        Statement stmt = null;
        try {
            stmt = dbConnection.createStatement();
            stmt.executeQuery(Consts.DB_KEEPALIVE_TEST_STATEMENT);
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDebugGameLogMessage(
                    FileDebugGameLogMessage.DebugLogType.DEBUGLOGTYPE_INFO,
                    "DB Keep Alive Success."));
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBBehaviorGameLogMessage(DBBehaviorGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "action" + dateStringBuffer.toString();
        	String tableName = "action";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`action`,`time`,`intdata1`,`intdata2`,`intdata3`,`intdata4`,`intdata5`,`intdata6`,`intdata7`,`intdata8`,`intdata9`,`intdata10`,`longdata1`,`longdata2`,`stringdata1`,`stringdata2`,`blobdata`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_action_log(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.action);
            stmt.setTimestamp(4, new Timestamp(message.time));
            stmt.setInt(5, message.i1);
            stmt.setInt(6, message.i2);
            stmt.setInt(7, message.i3);
            stmt.setInt(8, message.i4);
            stmt.setInt(9, message.i5);
            stmt.setInt(10, message.i6);
            stmt.setInt(11, message.i7);
            stmt.setInt(12, message.i8);
            stmt.setInt(13, message.i9);
            stmt.setInt(14, message.i10);
            stmt.setLong(15, message.l1);
            stmt.setLong(16, message.l2);

            if (message.str1 != null) {
                stmt.setString(17, message.str1);
            } else {
                stmt.setNull(17, java.sql.Types.VARCHAR);
            }

            if (message.str2 != null) {
                stmt.setString(18, message.str2);
            } else {
                stmt.setNull(18, java.sql.Types.VARCHAR);
            }

            if (message.bytes!=null) {
                Blob blob = dbConnection.createBlob();
                byte[] bytes = message.bytes;
                blob.setBytes(1, bytes);
                stmt.setBlob(19, blob);
            } else {
                stmt.setNull(19, java.sql.Types.BINARY);
            }

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBCreateCharGameLogMessage(DBCreateCharGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "createchar" + dateStringBuffer.toString();
        	String tableName = "createchar";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`leaderCardId`,`time`) VALUES(?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_createchar_log(?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.leaderCardId);
            stmt.setTimestamp(4, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBLoginLogoutGameLogMessage(DBLoginLogoutGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "loginlogout" + dateStringBuffer.toString();
        	String tableName = "loginlogout";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`lv`,`islogin`,`ip`,`duration`,`time`) VALUES(?,?,?,?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_loginlogout_log(?,?,?,?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.lv);
            stmt.setBoolean(4, message.islogin);
            stmt.setInt(5, message.ip);
            stmt.setLong(6, message.duration);
            stmt.setTimestamp(7, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBOnlineNumGameLogMessage(DBOnlineNumGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "onlinenum" + dateStringBuffer.toString();
        	String tableName = "onlinenum";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`onlinenum`,`time`) VALUES(?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_onlinenum_log(?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.onlineNum);
            stmt.setTimestamp(3, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBCashGameLogMessage(DBCashGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "cash" + dateStringBuffer.toString();
        	String tableName = "cash";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`cash`,`activity`,`time`) VALUES(?,?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_cash_log(?,?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.qbNum);
            stmt.setInt(4, 0);
            stmt.setTimestamp(5, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBGoldGameLogMessage(DBGoldGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "gold" + dateStringBuffer.toString();
        	String tableName = "gold";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`type`,`subtype`,`change1`,`change2`,`remain1`,`remain2`,`lv`,`time`) VALUES(?,?,?,?,?,?,?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_gold_log(?,?,?,?,?,?,?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.mainType);
            stmt.setInt(4, message.subtype);
            stmt.setInt(5, message.change1);
            stmt.setInt(6, message.change2);
            stmt.setInt(7, message.remain1);
            stmt.setInt(8, message.remain2);
            stmt.setInt(9, message.lv);
            stmt.setTimestamp(10, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }

    private void handleDBSilverGameLogMessage(DBSilverGameLogMessage message) {
        //CallableStatement stmt = null;
        PreparedStatement stmt = null;
        try {
            //StringBuffer dateStringBuffer = new SimpleDateFormat("yyyyMMdd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            //String tableName = "silver" + dateStringBuffer.toString();
        	String tableName = "silver";
            String sql = "INSERT INTO " + tableName + "(`serverid`,`cid`,`type`,`subtype`,`change`,`remain`,`time`) VALUES(?,?,?,?,?,?,?)";
            stmt = dbConnection.prepareStatement(sql);
            //stmt = dbConnection.prepareCall("{call add_silver_log(?,?,?,?,?,?,?)}");
            stmt.setInt(1, GameServer.INSTANCE.serverId);
            stmt.setInt(2, message.playerId);
            stmt.setInt(3, message.mainType);
            stmt.setInt(4, message.subtype);
            stmt.setInt(5, message.change);
            stmt.setLong(6, message.remain);
            stmt.setTimestamp(7, new Timestamp(message.time));

            stmt.executeUpdate();
        } catch (SQLException ex) {
            GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ex) {
                    GameLogger.getlogger().log(GameLogMessageBuilder.buildFileDBErrorGameLogMessage(StackTraceUtil.getStackTrace(ex)));
                }
            }
        }
    }
}
